Conditions | 13 |
Paths | 113 |
Total Lines | 279 |
Code Lines | 144 |
Lines | 0 |
Ratio | 0 % |
Changes | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like jquery.flot.pie.js ➔ ... ➔ draw often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | /* Flot plugin for rendering pie charts. |
||
253 | function draw(plot, newCtx) { |
||
254 | |||
255 | if (!target) { |
||
256 | return; // if no series were passed |
||
257 | } |
||
258 | |||
259 | var canvasWidth = plot.getPlaceholder().width(), |
||
260 | canvasHeight = plot.getPlaceholder().height(), |
||
261 | legendWidth = target.children().filter(".legend").children().width() || 0; |
||
262 | |||
263 | ctx = newCtx; |
||
264 | |||
265 | // WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE! |
||
266 | |||
267 | // When combining smaller slices into an 'other' slice, we need to |
||
268 | // add a new series. Since Flot gives plugins no way to modify the |
||
269 | // list of series, the pie plugin uses a hack where the first call |
||
270 | // to processDatapoints results in a call to setData with the new |
||
271 | // list of series, then subsequent processDatapoints do nothing. |
||
272 | |||
273 | // The plugin-global 'processed' flag is used to control this hack; |
||
274 | // it starts out false, and is set to true after the first call to |
||
275 | // processDatapoints. |
||
276 | |||
277 | // Unfortunately this turns future setData calls into no-ops; they |
||
278 | // call processDatapoints, the flag is true, and nothing happens. |
||
279 | |||
280 | // To fix this we'll set the flag back to false here in draw, when |
||
281 | // all series have been processed, so the next sequence of calls to |
||
282 | // processDatapoints once again starts out with a slice-combine. |
||
283 | // This is really a hack; in 0.9 we need to give plugins a proper |
||
284 | // way to modify series before any processing begins. |
||
285 | |||
286 | processed = false; |
||
287 | |||
288 | // calculate maximum radius and center point |
||
289 | |||
290 | maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2; |
||
291 | centerTop = canvasHeight / 2 + options.series.pie.offset.top; |
||
292 | centerLeft = canvasWidth / 2; |
||
293 | |||
294 | if (options.series.pie.offset.left == "auto") { |
||
295 | if (options.legend.position.match("w")) { |
||
296 | centerLeft += legendWidth / 2; |
||
297 | } else { |
||
298 | centerLeft -= legendWidth / 2; |
||
299 | } |
||
300 | if (centerLeft < maxRadius) { |
||
301 | centerLeft = maxRadius; |
||
302 | } else if (centerLeft > canvasWidth - maxRadius) { |
||
303 | centerLeft = canvasWidth - maxRadius; |
||
304 | } |
||
305 | } else { |
||
306 | centerLeft += options.series.pie.offset.left; |
||
307 | } |
||
308 | |||
309 | var slices = plot.getData(), |
||
310 | attempts = 0; |
||
311 | |||
312 | // Keep shrinking the pie's radius until drawPie returns true, |
||
313 | // indicating that all the labels fit, or we try too many times. |
||
314 | |||
315 | do { |
||
316 | if (attempts > 0) { |
||
317 | maxRadius *= REDRAW_SHRINK; |
||
318 | } |
||
319 | attempts += 1; |
||
320 | clear(); |
||
321 | if (options.series.pie.tilt <= 0.8) { |
||
322 | drawShadow(); |
||
323 | } |
||
324 | } while (!drawPie() && attempts < REDRAW_ATTEMPTS) |
||
325 | |||
326 | if (attempts >= REDRAW_ATTEMPTS) { |
||
327 | clear(); |
||
328 | target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>"); |
||
329 | } |
||
330 | |||
331 | if (plot.setSeries && plot.insertLegend) { |
||
332 | plot.setSeries(slices); |
||
333 | plot.insertLegend(); |
||
334 | } |
||
335 | |||
336 | // we're actually done at this point, just defining internal functions at this point |
||
337 | |||
338 | function clear() { |
||
339 | ctx.clearRect(0, 0, canvasWidth, canvasHeight); |
||
340 | target.children().filter(".pieLabel, .pieLabelBackground").remove(); |
||
341 | } |
||
342 | |||
343 | function drawShadow() { |
||
344 | |||
345 | var shadowLeft = options.series.pie.shadow.left; |
||
346 | var shadowTop = options.series.pie.shadow.top; |
||
347 | var edge = 10; |
||
348 | var alpha = options.series.pie.shadow.alpha; |
||
349 | var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; |
||
350 | |||
351 | if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) { |
||
352 | return; // shadow would be outside canvas, so don't draw it |
||
353 | } |
||
354 | |||
355 | ctx.save(); |
||
356 | ctx.translate(shadowLeft,shadowTop); |
||
357 | ctx.globalAlpha = alpha; |
||
358 | ctx.fillStyle = "#000"; |
||
359 | |||
360 | // center and rotate to starting position |
||
361 | |||
362 | ctx.translate(centerLeft,centerTop); |
||
363 | ctx.scale(1, options.series.pie.tilt); |
||
364 | |||
365 | //radius -= edge; |
||
366 | |||
367 | for (var i = 1; i <= edge; i++) { |
||
368 | ctx.beginPath(); |
||
369 | ctx.arc(0, 0, radius, 0, Math.PI * 2, false); |
||
370 | ctx.fill(); |
||
371 | radius -= i; |
||
372 | } |
||
373 | |||
374 | ctx.restore(); |
||
375 | } |
||
376 | |||
377 | function drawPie() { |
||
378 | |||
379 | var startAngle = Math.PI * options.series.pie.startAngle; |
||
380 | var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; |
||
381 | |||
382 | // center and rotate to starting position |
||
383 | |||
384 | ctx.save(); |
||
385 | ctx.translate(centerLeft,centerTop); |
||
386 | ctx.scale(1, options.series.pie.tilt); |
||
387 | //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera |
||
388 | |||
389 | // draw slices |
||
390 | |||
391 | ctx.save(); |
||
392 | var currentAngle = startAngle; |
||
393 | for (var i = 0; i < slices.length; ++i) { |
||
394 | slices[i].startAngle = currentAngle; |
||
395 | drawSlice(slices[i].angle, slices[i].color, true); |
||
396 | } |
||
397 | ctx.restore(); |
||
398 | |||
399 | // draw slice outlines |
||
400 | |||
401 | if (options.series.pie.stroke.width > 0) { |
||
402 | ctx.save(); |
||
403 | ctx.lineWidth = options.series.pie.stroke.width; |
||
404 | currentAngle = startAngle; |
||
405 | for (var i = 0; i < slices.length; ++i) { |
||
406 | drawSlice(slices[i].angle, options.series.pie.stroke.color, false); |
||
407 | } |
||
408 | ctx.restore(); |
||
409 | } |
||
410 | |||
411 | // draw donut hole |
||
412 | |||
413 | drawDonutHole(ctx); |
||
414 | |||
415 | ctx.restore(); |
||
416 | |||
417 | // Draw the labels, returning true if they fit within the plot |
||
418 | |||
419 | if (options.series.pie.label.show) { |
||
420 | return drawLabels(); |
||
421 | } else return true; |
||
422 | |||
423 | function drawSlice(angle, color, fill) { |
||
424 | |||
425 | if (angle <= 0 || isNaN(angle)) { |
||
426 | return; |
||
427 | } |
||
428 | |||
429 | if (fill) { |
||
430 | ctx.fillStyle = color; |
||
431 | } else { |
||
432 | ctx.strokeStyle = color; |
||
433 | ctx.lineJoin = "round"; |
||
434 | } |
||
435 | |||
436 | ctx.beginPath(); |
||
437 | if (Math.abs(angle - Math.PI * 2) > 0.000000001) { |
||
438 | ctx.moveTo(0, 0); // Center of the pie |
||
439 | } |
||
440 | |||
441 | //ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera |
||
442 | ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false); |
||
443 | ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false); |
||
444 | ctx.closePath(); |
||
445 | //ctx.rotate(angle); // This doesn't work properly in Opera |
||
446 | currentAngle += angle; |
||
447 | |||
448 | if (fill) { |
||
449 | ctx.fill(); |
||
450 | } else { |
||
451 | ctx.stroke(); |
||
452 | } |
||
453 | } |
||
454 | |||
455 | function drawLabels() { |
||
456 | |||
457 | var currentAngle = startAngle; |
||
458 | var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius; |
||
459 | |||
460 | for (var i = 0; i < slices.length; ++i) { |
||
461 | if (slices[i].percent >= options.series.pie.label.threshold * 100) { |
||
462 | if (!drawLabel(slices[i], currentAngle, i)) { |
||
463 | return false; |
||
464 | } |
||
465 | } |
||
466 | currentAngle += slices[i].angle; |
||
467 | } |
||
468 | |||
469 | return true; |
||
470 | |||
471 | function drawLabel(slice, startAngle, index) { |
||
472 | |||
473 | if (slice.data[0][1] == 0) { |
||
474 | return true; |
||
475 | } |
||
476 | |||
477 | // format label text |
||
478 | |||
479 | var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter; |
||
480 | |||
481 | if (lf) { |
||
482 | text = lf(slice.label, slice); |
||
483 | } else { |
||
484 | text = slice.label; |
||
485 | } |
||
486 | |||
487 | if (plf) { |
||
488 | text = plf(text, slice); |
||
489 | } |
||
490 | |||
491 | var halfAngle = ((startAngle + slice.angle) + startAngle) / 2; |
||
492 | var x = centerLeft + Math.round(Math.cos(halfAngle) * radius); |
||
493 | var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; |
||
494 | |||
495 | var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>"; |
||
496 | target.append(html); |
||
497 | |||
498 | var label = target.children("#pieLabel" + index); |
||
499 | var labelTop = (y - label.height() / 2); |
||
500 | var labelLeft = (x - label.width() / 2); |
||
501 | |||
502 | label.css("top", labelTop); |
||
503 | label.css("left", labelLeft); |
||
504 | |||
505 | // check to make sure that the label is not outside the canvas |
||
506 | |||
507 | if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) { |
||
508 | return false; |
||
509 | } |
||
510 | |||
511 | if (options.series.pie.label.background.opacity != 0) { |
||
512 | |||
513 | // put in the transparent background separately to avoid blended labels and label boxes |
||
514 | |||
515 | var c = options.series.pie.label.background.color; |
||
516 | |||
517 | if (c == null) { |
||
518 | c = slice.color; |
||
519 | } |
||
520 | |||
521 | var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;"; |
||
522 | $("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>") |
||
523 | .css("opacity", options.series.pie.label.background.opacity) |
||
524 | .insertBefore(label); |
||
525 | } |
||
526 | |||
527 | return true; |
||
528 | } // end individual label function |
||
529 | } // end drawLabels function |
||
530 | } // end drawPie function |
||
531 | } // end draw function |
||
532 | |||
821 |